﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace Kang.ExtendMethod
{
    /// <summary>
    /// 在日常开发中，我们对身份证的验证主要是正则方式（位数，数字范围等），但是中国身份证，尤其18位身份证每一位都有严格规定，并且最后一位为校验位。而我们在实际应用中，针对身份证的验证理应严格至此。
    /// </summary>
    public static class IdcardExtendMethod
    {
        private static readonly int CHINA_ID_MIN_LENGTH = 15;
        private static readonly int CHINA_ID_MAX_LENGTH = 18;
        private static readonly int[] POWER = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
        private static readonly Dictionary<string, string> CITY_CODES = new Dictionary<string, string>()
        {
            { "11", "北京" },
            { "12", "天津" },
            { "13", "河北" },
            { "14", "山西" },
            { "15", "内蒙古" },
            { "21", "辽宁" },
            { "22", "吉林" },
            { "23", "黑龙江" },
            { "31", "上海" },
            { "32", "江苏" },
            { "33", "浙江" },
            { "34", "安徽" },
            { "35", "福建" },
            { "36", "江西" },
            { "37", "山东" },
            { "41", "河南" },
            { "42", "湖北" },
            { "43", "湖南" },
            { "44", "广东" },
            { "45", "广西" },
            { "46", "海南" },
            { "50", "重庆" },
            { "51", "四川" },
            { "52", "贵州" },
            { "53", "云南" },
            { "54", "西藏" },
            { "61", "陕西" },
            { "62", "甘肃" },
            { "63", "青海" },
            { "64", "宁夏" },
            { "65", "新疆" },
            { "71", "台湾" },
            { "81", "香港" },
            { "82", "澳门" },
            { "83", "台湾" },
            { "91", "国外" },
        };
        private static readonly Dictionary<char, int> TW_FIRST_CODE = new Dictionary<char, int>()
        {
            { 'A', 10 },
            { 'B', 11 },
            { 'C', 12 },
            { 'D', 13 },
            { 'E', 14 },
            { 'F', 15 },
            { 'G', 16 },
            { 'H', 17 },
            { 'J', 18 },
            { 'K', 19 },
            { 'L', 20 },
            { 'M', 21 },
            { 'N', 22 },
            { 'P', 23 },
            { 'Q', 24 },
            { 'R', 25 },
            { 'S', 26 },
            { 'T', 27 },
            { 'U', 28 },
            { 'V', 29 },
            { 'X', 30 },
            { 'Y', 31 },
            { 'W', 32 },
            { 'Z', 33 },
            { 'I', 34 },
            { 'O', 35 },
        };

        /// <summary>
        /// 验证身份证是否合法
        /// </summary>
        /// <param name="str">身份证号码</param>
        /// <returns>Trim后的字符串</returns>
        public static bool IsValidCard(this string str)
        {
            if (str.IsBlank())
            {
                return false;
            }
            string card = str.StringTrim();
            if (card.Length != 18 && card.Length != 15)
            {
                return false;
            }
            int length = card.Length;
            switch (length)
            {
                // 18位身份证
                case 18:
                    return IsValidCard18(card);
                // 15位身份证
                case 15:
                    return IsValidCard15(card);
                case 10:
                    {
                        // 10位身份证，港澳台地区
                        string[] cardVal = IsValidCard10(card);
                        return null != cardVal && "true" == cardVal[2];
                    }
                default:
                    return false;
            }
        }

	    // 将15位身份证号码转换为18位
	    // 
	    // @param idCard 15位身份编码
	    // @return 18位身份编码
	    // 
		public static string Convert15To18(string idCard)
		{
			StringBuilder idCard18;
            if (idCard.IsBlank())
            {
				return null;
            }
			if (idCard.Length != CHINA_ID_MIN_LENGTH)
			{
				return null;
			}
			if (idCard.IsNumber())
			{
				// 获取出生年月日
				string birthday = idCard.Substring(6, 6);
				DateTime? birthDate = birthday.ToDateTime("yyMMdd");
                if (birthDate == null)
                {
					return null;
				}
				// 获取出生年(完全表现形式,如：2010)
				int sYear = birthDate.Value.Year;
				if (sYear > 2000)
				{
					sYear -= 100;
				}
				idCard18 = new StringBuilder().Append(idCard, 0, 6).Append(sYear).Append(idCard.Substring(8));
				// 获取校验位
				char sVal = GetCheckCode18(idCard18.ToString());
				idCard18.Append(sVal);
			}
			else
			{
				return null;
			}
			return idCard18.ToString();
		}

		/// 
		/// <p>
		/// 判断18位身份证的合法性
		/// </p>
		/// 根据〖中华人民共和国国家标准GB11643-1999〗中有关公民身份号码的规定，公民身份号码是特征组合码，由十七位数字本体码和一位数字校验码组成。<br>
		/// 排列顺序从左至右依次为：六位数字地址码，八位数字出生日期码，三位数字顺序码和一位数字校验码。
		/// <p>
		/// 顺序码: 表示在同一地址码所标识的区域范围内，对同年、同月、同 日出生的人编定的顺序号，顺序码的奇数分配给男性，偶数分配 给女性。
		/// </p>
		/// <ol>
		/// <li>第1、2位数字表示：所在省份的代码</li>
		/// <li>第3、4位数字表示：所在城市的代码</li>
		/// <li>第5、6位数字表示：所在区县的代码</li>
		/// <li>第7~14位数字表示：出生年、月、日</li>
		/// <li>第15、16位数字表示：所在地的派出所的代码</li>
		/// <li>第17位数字表示性别：奇数表示男性，偶数表示女性</li>
		/// <li>第18位数字是校检码，用来检验身份证的正确性。校检码可以是0~9的数字，有时也用x表示</li>
		/// </ol>
		/// <p>
		/// 第十八位数字(校验码)的计算方法为：
		/// <ol>
		/// <li>将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为：7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2</li>
		/// <li>将这17位数字和系数相乘的结果相加</li>
		/// <li>用加出来和除以11，看余数是多少</li>
		/// <li>余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2</li>
		/// <li>通过上面得知如果余数是2，就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10，身份证的最后一位号码就是2</li>
		/// </ol>
		/// <ol>
		/// 	     <li>香港人在大陆的身份证，【810000】开头；同样可以直接获取到 性别、出生日期</li>
		/// 	     <li>81000019980902013X: 文绎循 男 1998-09-02</li>
		/// 	     <li>810000201011210153: 辛烨 男 2010-11-21</li>
		/// 	 </ol>
		/// 	 <ol>
		///       <li>澳门人在大陆的身份证，【820000】开头；同样可以直接获取到 性别、出生日期</li>
		///       <li>820000200009100032: 黄敬杰 男 2000-09-10</li>
		///  </ol>
		///  <ol>
		///     <li>台湾人在大陆的身份证，【830000】开头；同样可以直接获取到 性别、出生日期</li>
		///     <li>830000200209060065: 王宜妃 女 2002-09-06</li>
		///     <li>830000194609150010: 苏建文 男 1946-09-14</li>
		///     <li>83000019810715006X: 刁婉琇 女 1981-07-15</li>
		/// </ol>
		/// 
		/// @param idcard 待验证的身份证
		/// @return 是否有效的18位身份证，忽略x的大小写
		/// 
		public static bool IsValidCard18(string idcard)
		{
			return IsValidCard18(idcard, true);
		}

		/// 
		/// <p>
		/// 判断18位身份证的合法性
		/// </p>
		/// 根据〖中华人民共和国国家标准GB11643-1999〗中有关公民身份号码的规定，公民身份号码是特征组合码，由十七位数字本体码和一位数字校验码组成。<br>
		/// 排列顺序从左至右依次为：六位数字地址码，八位数字出生日期码，三位数字顺序码和一位数字校验码。
		/// <p>
		/// 顺序码: 表示在同一地址码所标识的区域范围内，对同年、同月、同 日出生的人编定的顺序号，顺序码的奇数分配给男性，偶数分配 给女性。
		/// </p>
		/// <ol>
		/// <li>第1、2位数字表示：所在省份的代码</li>
		/// <li>第3、4位数字表示：所在城市的代码</li>
		/// <li>第5、6位数字表示：所在区县的代码</li>
		/// <li>第7~14位数字表示：出生年、月、日</li>
		/// <li>第15、16位数字表示：所在地的派出所的代码</li>
		/// <li>第17位数字表示性别：奇数表示男性，偶数表示女性</li>
		/// <li>第18位数字是校检码，用来检验身份证的正确性。校检码可以是0~9的数字，有时也用x表示</li>
		/// </ol>
		/// <p>
		/// 第十八位数字(校验码)的计算方法为：
		/// <ol>
		/// <li>将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为：7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2</li>
		/// <li>将这17位数字和系数相乘的结果相加</li>
		/// <li>用加出来和除以11，看余数是多少</li>
		/// <li>余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2</li>
		/// <li>通过上面得知如果余数是2，就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10，身份证的最后一位号码就是2</li>
		/// </ol>
		/// 
		/// @param idcard     待验证的身份证
		/// @param ignoreCase 是否忽略大小写。{@code true}则忽略X大小写，否则严格匹配大写。
		/// @return 是否有效的18位身份证
		/// @since 5.5.7
		/// 
		public static bool IsValidCard18(string idcard, bool ignoreCase)
		{
			if (CHINA_ID_MAX_LENGTH != idcard.Length)
			{
				return false;
			}

			// 省份
			string proCode = idcard.Substring(0, 2);
			if (null == CITY_CODES[proCode])
			{
				return false;
			}

			//校验生日
			if (idcard.Substring(6, 8).ToInteger() < 19000101)
			{
				return false;
			}

			// 前17位
			string code17 = idcard.Substring(0, 17);
			if (code17.IsNumber())
			{
				// 获取校验位
				char val = GetCheckCode18(code17);
				// 第18位
				return $"{val}".ToUpper() == $"{idcard[17]}".ToUpper();
			}
			return false;
		}

		/// 
		/// 验证15位身份编码是否合法
		/// 
		/// @param idcard 身份编码
		/// @return 是否合法
		/// 
		public static bool IsValidCard15(string idcard)
		{
			if (CHINA_ID_MIN_LENGTH != idcard.Length)
			{
				return false;
			}
			if (idcard.IsNumber())
			{
				// 省份
				string proCode = idcard.Substring(0, 2);
				if (null == CITY_CODES[proCode])
				{
					return false;
				}

				//校验生日（两位年份，补充为19XX）
				return false != $"19{idcard.Substring(6, 6)}".ToInteger() < 19000101;
			}
			else
			{
				return false;
			}
		}

		/// 
		/// 验证10位身份编码是否合法
		/// 
		/// @param idcard 身份编码
		/// @return 身份证信息数组
		/// <p>
		/// [0] - 台湾、澳门、香港 [1] - 性别(男M,女F,未知N) [2] - 是否合法(合法true,不合法false) 若不是身份证件号码则返回null
		/// </p>
		/// 
		public static string[] IsValidCard10(string idcard)
		{
			if (idcard.IsBlank())
			{
				return null;
			}
			string[] info = new string[3];
			string card = idcard.ReplaceAll(new string[] { "(", ")", "（", "）" }, new string[] { "", "", "", "" });
			if (card.Length != 8 && card.Length != 9 && idcard.Length != 10)
			{
				return null;
			}
			if (idcard.RegexIs("^[a-zA-Z][0-9]{9}$"))
			{ // 台湾
				info[0] = "台湾";
				char char2 = idcard[1];
				if ('1' == char2)
				{
					info[1] = "M";
				}
				else if ('2' == char2)
				{
					info[1] = "F";
				}
				else
				{
					info[1] = "N";
					info[2] = "false";
					return info;
				}
				info[2] = IsValidTWCard(idcard) ? "true" : "false";
			}
			else if (idcard.RegexIs("^[157][0-9]{6}\\(?[0-9A-Z]\\)?$"))
			{ // 澳门
				info[0] = "澳门";
				info[1] = "N";
				info[2] = "true";
			}
			else if (idcard.RegexIs("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$"))
			{ // 香港
				info[0] = "香港";
				info[1] = "N";
				info[2] = IsValidHKCard(idcard) ? "true" : "false";
			}
			else
			{
				return null;
			}
			return info;
		}

		/// 
		/// 验证台湾身份证号码
		/// 
		/// @param idcard 身份证号码
		/// @return 验证码是否符合
		/// 
		public static bool IsValidTWCard(this string idcard)
		{
			if (null == idcard || idcard.Length != 10)
			{
				return false;
			}
            if (!TW_FIRST_CODE.ContainsKey(idcard[0]))
            {
				return false;
			}
			int iStart = TW_FIRST_CODE[idcard[0]];
			int sum = iStart / 10 + (iStart % 10) * 9;

			string mid = idcard.Substring(1, 8);
			char[] chars = mid.ToCharArray();
			int iflag = 8;
			foreach (char c in chars)
			{
				sum += $"{c}".ToInteger() * iflag;
				iflag--;
			}

			string end = idcard.Substring(9, 1);
			return (sum % 10 == 0 ? 0 : (10 - sum % 10)) == end.ToInteger();
		}

		/// 
		/// 验证香港身份证号码(存在Bug，部份特殊身份证无法检查)
		/// 身份证前2位为英文字符，如果只出现一个英文字符则表示第一位是空格，对应数字58 前2位英文字符A-Z分别对应数字10-35 最后一位校验码为0-9的数字加上字符"A"，"A"代表10
		/// 将身份证号码全部转换为数字，分别对应乘9-1相加的总和，整除11则证件号码有效
		/// @param idcard 身份证号码
		/// @return 验证码是否符合
		/// 
		public static bool IsValidHKCard(this string idcard)
		{
			string card = idcard.ReplaceAll(new string[] { "(", ")", "（", "）" }, new string[] { "", "", "", "" });
			int sum;
			if (card.Length == 9)
			{
				sum = (card.ToUpper()[0] - 55) * 9 + (card.ToUpper()[1] - 55) * 8;
				card = card.Substring(1, 8);
			}
			else
			{
				sum = 522 + (card.ToUpper()[0] - 55) * 8;
			}

			// 首字母A-Z，A表示1，以此类推
			string mid = card.Substring(1, 6);
			string end = card.Substring(7, 1);
			char[] chars = mid.ToCharArray();
			int iflag = 7;
			foreach (char c in chars)
			{
				sum = sum + $"{c}".ToInteger() * iflag;
				iflag--;
			}
			if ("A" == end.ToUpper())
			{
				sum += 10;
			}
			else
			{
				sum += end.ToInteger();
			}
			return sum % 11 == 0;
		}

		/// 
		/// 根据身份编号获取生日，只支持15或18位身份证号码
		/// 
		/// @param idcard 身份编号
		/// @return 生日(yyyyMMdd)
		/// @see #getBirth(string)
		/// 
		public static string GetBirthByIdCard(this string idcard)
		{
			return GetBirth(idcard);
		}

		/// 
		/// 根据身份编号获取生日，只支持15或18位身份证号码
		/// 
		/// @param idCard 身份编号
		/// @return 生日(yyyyMMdd)
		/// 
		public static string GetBirth(this string idCard)
		{
            if (idCard.IsBlank())
            {
				throw new ArgumentNullException("Idcard is null");
			}
			int len = idCard.Length;
			if (len < CHINA_ID_MIN_LENGTH)
			{
				throw new ArgumentException("ID Card length must be 15 or 18");
			}
			else if (len == CHINA_ID_MIN_LENGTH)
			{
				idCard = Convert15To18(idCard);
			}

			return idCard.Substring(6, 8);
		}

		/// 
		/// 从身份证号码中获取生日日期，只支持15或18位身份证号码
		/// 
		/// @param idCard 身份证号码
		/// @return 日期
		/// 
		public static DateTime? GetBirthDate(this string idCard)
		{
			string birthByIdCard = GetBirthByIdCard(idCard);
			return null == birthByIdCard ? null : birthByIdCard.ToDateTime("yyyyMMdd");
		}

		/// 
		/// 根据身份编号获取年龄，只支持15或18位身份证号码
		/// 
		/// @param idcard 身份编号
		/// @return 年龄
		/// 
		public static int GetAgeByIdCard(this string idcard)
		{
			return GetAgeByIdCard(idcard, DateTime.Now);
		}

		/// 
		/// 根据身份编号获取指定日期当时的年龄年龄，只支持15或18位身份证号码
		/// 
		/// @param idcard        身份编号
		/// @param dateToCompare 以此日期为界，计算年龄。
		/// @return 年龄
		/// 
		public static int GetAgeByIdCard(this string idcard, DateTime dateToCompare)
		{
			string birth = GetBirthByIdCard(idcard);
			DateTime? birthDate = birth.ToDateTime("yyyyMMdd");
            if (birthDate == null)
            {
				throw new ArgumentException("ID Card birth substring is error");
			}
			if (dateToCompare.Year < birthDate.Value.Year)
            {
				throw new ArgumentException("Date cannot be later than birth date");
			}
			return birthDate.Age(dateToCompare);
		}

		/// 
		/// 根据身份编号获取生日年，只支持15或18位身份证号码
		/// 
		/// @param idcard 身份编号
		/// @return 生日(yyyy)
		/// 
		public static int GetYearByIdCard(this string idcard)
		{
			int len = idcard.Length;
			if (len < CHINA_ID_MIN_LENGTH)
			{
				throw new ArgumentException("ID Card length must be 15 or 18");
			}
			else if (len == CHINA_ID_MIN_LENGTH)
			{
				idcard = Convert15To18(idcard);
			}
			return idcard.Substring(6, 4).ToInteger();
		}

		/// 
		/// 根据身份编号获取生日月，只支持15或18位身份证号码
		/// 
		/// @param idcard 身份编号
		/// @return 生日(MM)
		/// 
		public static int GetMonthByIdCard(this string idcard)
		{
			int len = idcard.Length;
			if (len < CHINA_ID_MIN_LENGTH)
			{
				throw new ArgumentException("ID Card length must be 15 or 18");
			}
			else if (len == CHINA_ID_MIN_LENGTH)
			{
				idcard = Convert15To18(idcard);
			}
			return idcard.Substring(10, 2).ToInteger();
		}

		/// 
		/// 根据身份编号获取生日天，只支持15或18位身份证号码
		/// 
		/// @param idcard 身份编号
		/// @return 生日(dd)
		/// 
		public static int GetDayByIdCard(this string idcard)
		{
			int len = idcard.Length;
			if (len < CHINA_ID_MIN_LENGTH)
			{
				throw new ArgumentException("ID Card length must be 15 or 18");
			}
			else if (len == CHINA_ID_MIN_LENGTH)
			{
				idcard = Convert15To18(idcard);
			}
			return idcard.Substring(12, 2).ToInteger();
		}

		/// 
		/// 根据身份编号获取性别，只支持15或18位身份证号码
		/// 
		/// @param idcard 身份编号
		/// @return 性别(0 : 男 ， 1 : 女)
		/// 
		public static int GetGenderByIdCard(this string idcard)
		{
            if (idcard.IsBlank())
            {
				throw new ArgumentNullException("Idcard is null");
            }
			int len = idcard.Length;
			if (len < CHINA_ID_MIN_LENGTH)
			{
				throw new ArgumentException("ID Card length must be 15 or 18");
			}

			if (len == CHINA_ID_MIN_LENGTH)
			{
				idcard = Convert15To18(idcard);
			}
			char sCardChar = idcard[16];
			return (sCardChar % 2 != 0) ? 0 : 1;
		}

		/// 
		/// 根据身份编号获取户籍省份编码，只支持15或18位身份证号码
		/// 
		/// @param idcard 身份编码
		/// @return 省份编码
		/// @since 5.7.2
		/// 
		public static string GetProvinceCodeByIdCard(this string idcard)
		{
			int len = idcard.Length;
			if (len == CHINA_ID_MIN_LENGTH || len == CHINA_ID_MAX_LENGTH)
			{
				return idcard.Substring(0, 2);
			}
			return null;
		}

		/// 
		/// 根据身份编号获取户籍省份，只支持15或18位身份证号码
		/// 
		/// @param idcard 身份编码
		/// @return 省份名称。
		/// 
		public static string GetProvinceByIdCard(this string idcard)
		{
			string code = GetProvinceCodeByIdCard(idcard);
			if (code.IsNotBlank())
			{
				return CITY_CODES[code];
			}
			return null;
		}

		/// 
		/// 根据身份编号获取地市级编码，只支持15或18位身份证号码<br>
		/// 获取编码为4位
		/// 
		/// @param idcard 身份编码
		/// @return 地市级编码
		/// 
		public static string GetCityCodeByIdCard(this string idcard)
		{
			int len = idcard.Length;
			if (len == CHINA_ID_MIN_LENGTH || len == CHINA_ID_MAX_LENGTH)
			{
				return idcard.Substring(0, 4);
			}
			return null;
		}

		/// 
		/// 根据身份编号获取区县级编码，只支持15或18位身份证号码<br>
		/// 获取编码为6位
		/// 
		/// @param idcard 身份编码
		/// @return 地市级编码
		/// @since 5.8.0
		/// 
		public static string GetDistrictCodeByIdCard(this string idcard)
		{
			int len = idcard.Length;
			if (len == CHINA_ID_MIN_LENGTH || len == CHINA_ID_MAX_LENGTH)
			{
				return idcard.Substring(0, 6);
			}
			return null;
		}

		/// 
		/// 隐藏指定位置的几个身份证号数字为“*”
		/// 
		/// @param idcard       身份证号
		/// @param startInclude 开始位置（包含）
		/// @param endExclude   长度
		/// @return 隐藏后的身份证号码
		/// @since 3.2.2
		/// 
		public static string Hide(string idcard, int startInclude, int length)
		{
			return idcard.Hide(startInclude, length);
		}

		/// 
		/// 获得18位身份证校验码
		/// 
		/// @param code17 18位身份证号中的前17位
		/// @return 第18位
		/// 
		private static char GetCheckCode18(string code17)
		{
			int sum = GetPowerSum(code17.ToCharArray());
			return GetCheckCode18(sum);
		}

		/// 
		/// 将power和值与11取模获得余数进行校验码判断
		/// 
		/// @param iSum 加权和
		/// @return 校验位
		/// 
		private static char GetCheckCode18(int iSum)
		{
			switch (iSum % 11)
			{
				case 10:
					return '2';
				case 9:
					return '3';
				case 8:
					return '4';
				case 7:
					return '5';
				case 6:
					return '6';
				case 5:
					return '7';
				case 4:
					return '8';
				case 3:
					return '9';
				case 2:
					return 'X';
				case 1:
					return '0';
				case 0:
					return '1';
				default:
					return ' ';
			}
		}

		/// 
		/// 将身份证的每位和对应位的加权因子相乘之后，再得到和值
		/// 
		/// @param iArr 身份证号码的数组
		/// @return 身份证编码
		/// 
		private static int GetPowerSum(char[] iArr)
		{
			int iSum = 0;
			if (POWER.Length == iArr.Length)
			{
				for (int i = 0; i < iArr.Length; i++)
				{
					iSum += $"{iArr[i]}".ToInteger() * POWER[i];
				}
			}
			return iSum;
		}
	}
}
